Merge "Add ability to pre-render thumbnails at upload time"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 25 Sep 2014 15:40:07 +0000 (15:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 25 Sep 2014 15:40:07 +0000 (15:40 +0000)
1  2 
includes/AutoLoader.php
includes/DefaultSettings.php
includes/upload/UploadBase.php

diff --combined includes/AutoLoader.php
@@@ -36,12 -36,8 +36,12 @@@ $wgAutoloadLocalClasses = array
        'AuthPluginUser' => 'includes/AuthPlugin.php',
        'Autopromote' => 'includes/Autopromote.php',
        'Block' => 'includes/Block.php',
 +      'BloomCache' => 'includes/cache/bloom/BloomCache.php',
 +      'BloomCacheRedis' => 'includes/cache/bloom/BloomCacheRedis.php',
 +      'BloomFilterTitleHasLogs' => 'includes/cache/bloom/BloomFilters.php',
 +      'CacheHelper' => 'includes/CacheHelper.php',
        'Category' => 'includes/Category.php',
 -      'Categoryfinder' => 'includes/Categoryfinder.php',
 +      'CategoryFinder' => 'includes/CategoryFinder.php',
        'CategoryViewer' => 'includes/CategoryViewer.php',
        'ChangeTags' => 'includes/ChangeTags.php',
        'ChannelFeed' => 'includes/Feed.php',
@@@ -68,7 -64,7 +68,7 @@@
        'DumpOutput' => 'includes/Export.php',
        'DumpPipeOutput' => 'includes/Export.php',
        'EditPage' => 'includes/EditPage.php',
 -      'EmailNotification' => 'includes/UserMailer.php',
 +      'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php',
        'Fallback' => 'includes/Fallback.php',
        'FauxRequest' => 'includes/WebRequest.php',
        'FauxResponse' => 'includes/WebResponse.php',
        'LinkFilter' => 'includes/LinkFilter.php',
        'MagicWord' => 'includes/MagicWord.php',
        'MagicWordArray' => 'includes/MagicWord.php',
 -      'MailAddress' => 'includes/UserMailer.php',
        'MediaWiki' => 'includes/MediaWiki.php',
        'MediaWikiVersionFetcher' => 'includes/MediaWikiVersionFetcher.php',
        'Message' => 'includes/Message.php',
        'MessageBlobStore' => 'includes/MessageBlobStore.php',
        'MimeMagic' => 'includes/MimeMagic.php',
 +      'MovePage' => 'includes/MovePage.php',
        'MWHookException' => 'includes/Hooks.php',
        'MWHttpRequest' => 'includes/HttpFunctions.php',
        'MWNamespace' => 'includes/MWNamespace.php',
        'User' => 'includes/User.php',
        'UserArray' => 'includes/UserArray.php',
        'UserArrayFromResult' => 'includes/UserArrayFromResult.php',
 -      'UserMailer' => 'includes/UserMailer.php',
        'UserRightsProxy' => 'includes/UserRightsProxy.php',
        'WatchedItem' => 'includes/WatchedItem.php',
        'WebRequest' => 'includes/WebRequest.php',
        # includes/api
        'ApiBase' => 'includes/api/ApiBase.php',
        'ApiBlock' => 'includes/api/ApiBlock.php',
 +      'ApiClearHasMsg' => 'includes/api/ApiClearHasMsg.php',
        'ApiComparePages' => 'includes/api/ApiComparePages.php',
        'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php',
        'ApiDelete' => 'includes/api/ApiDelete.php',
        'ApiQueryAllPages' => 'includes/api/ApiQueryAllPages.php',
        'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php',
        'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php',
 +      'ApiQueryBacklinksprop' => 'includes/api/ApiQueryBacklinksprop.php',
        'ApiQueryBase' => 'includes/api/ApiQueryBase.php',
        'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php',
        'ApiQueryCategories' => 'includes/api/ApiQueryCategories.php',
        'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
        'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
        'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php',
 -      'ApiQueryRedirects' => 'includes/api/ApiQueryRedirects.php',
        'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
        'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
        'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
        'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
        'ICacheHelper' => 'includes/cache/CacheHelper.php',
        'LCStore' => 'includes/cache/LocalisationCache.php',
 -      'LCStoreAccel' => 'includes/cache/LocalisationCache.php',
        'LCStoreCDB' => 'includes/cache/LocalisationCache.php',
        'LCStoreDB' => 'includes/cache/LocalisationCache.php',
        'LCStoreNull' => 'includes/cache/LocalisationCache.php',
        'ConfigException' => 'includes/config/ConfigException.php',
        'ConfigFactory' => 'includes/config/ConfigFactory.php',
        'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php',
 +      'HashConfig' => 'includes/config/HashConfig.php',
 +      'MultiConfig' => 'includes/config/MultiConfig.php',
 +      'MutableConfig' => 'includes/config/MutableConfig.php',
  
        # includes/content
        'AbstractContent' => 'includes/content/AbstractContent.php',
 -      'ContentHandler' => 'includes/content/ContentHandler.php',
 +      'CodeContentHandler' => 'includes/content/CodeContentHandler.php',
        'Content' => 'includes/content/Content.php',
 -      'CssContentHandler' => 'includes/content/CssContentHandler.php',
 +      'ContentHandler' => 'includes/content/ContentHandler.php',
        'CssContent' => 'includes/content/CssContent.php',
 -      'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
 +      'CssContentHandler' => 'includes/content/CssContentHandler.php',
        'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
 -      'JSONContentHandler' => 'includes/content/JSONContentHandler.php',
 -      'JSONContent' => 'includes/content/JSONContent.php',
 +      'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
 +      'JsonContent' => 'includes/content/JsonContent.php',
 +      'JsonContentHandler' => 'includes/content/JsonContentHandler.php',
        'MessageContent' => 'includes/content/MessageContent.php',
        'MWContentSerializationException' => 'includes/content/ContentHandler.php',
 -      'TextContentHandler' => 'includes/content/TextContentHandler.php',
        'TextContent' => 'includes/content/TextContent.php',
 -      'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
 +      'TextContentHandler' => 'includes/content/TextContentHandler.php',
        'WikitextContent' => 'includes/content/WikitextContent.php',
 +      'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
  
        # includes/context
        'ContextSource' => 'includes/context/ContextSource.php',
        'UploadFromUrlJob' => 'includes/jobqueue/jobs/UploadFromUrlJob.php',
        'AssembleUploadChunksJob' => 'includes/jobqueue/jobs/AssembleUploadChunksJob.php',
        'PublishStashedFileJob' => 'includes/jobqueue/jobs/PublishStashedFileJob.php',
+       'ThumbnailRenderJob' => 'includes/jobqueue/jobs/ThumbnailRenderJob.php',
  
        # includes/jobqueue/utils
        'BacklinkJobUtils' => 'includes/jobqueue/utils/BacklinkJobUtils.php',
        'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
        'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
  
 +      # includes/mail
 +      'EmailNotification' => 'includes/mail/EmailNotification.php',
 +      'MailAddress' => 'includes/mail/MailAddress.php',
 +      'UserMailer' => 'includes/mail/UserMailer.php',
 +
        # includes/media
        'BitmapHandler' => 'includes/media/Bitmap.php',
        'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
        'SVGReader' => 'includes/media/SVGMetadataExtractor.php',
        'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
        'TiffHandler' => 'includes/media/Tiff.php',
 +      'TransformationalImageHandler' => 'includes/media/TransformationalImageHandler.php',
        'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
        'XCFHandler' => 'includes/media/XCF.php',
        'XMPInfo' => 'includes/media/XMPInfo.php',
                'includes/resourceloader/DerivativeResourceLoaderContext.php',
        'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
        'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
 +      'ResourceLoaderEditToolbarModule' => 'includes/resourceloader/ResourceLoaderEditToolbarModule.php',
        'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
        'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php',
        'ResourceLoaderFilePath' => 'includes/resourceloader/ResourceLoaderFilePath.php',
 -      'ResourceLoaderLESSFunctions' => 'includes/resourceloader/ResourceLoaderLESSFunctions.php',
        'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
        'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
        'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
        'SiteStore' => 'includes/site/SiteStore.php',
  
        # includes/skins
 -      'BaseTemplate' => 'includes/skins/SkinTemplate.php',
 -      'MediaWikiI18N' => 'includes/skins/SkinTemplate.php',
 -      'QuickTemplate' => 'includes/skins/SkinTemplate.php',
 +      'BaseTemplate' => 'includes/skins/BaseTemplate.php',
 +      'MediaWikiI18N' => 'includes/skins/MediaWikiI18N.php',
 +      'QuickTemplate' => 'includes/skins/QuickTemplate.php',
        'Skin' => 'includes/skins/Skin.php',
 +      'SkinApi' => 'includes/skins/SkinApi.php',
 +      'SkinApiTemplate' => 'includes/skins/SkinApiTemplate.php',
        'SkinException' => 'includes/skins/SkinException.php',
        'SkinFactory' => 'includes/skins/SkinFactory.php',
        'SkinFallback' => 'includes/skins/SkinFallback.php',
        'LoginForm' => 'includes/specials/SpecialUserlogin.php',
        'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php',
        'LongPagesPage' => 'includes/specials/SpecialLongpages.php',
 +      'MediaStatisticsPage' => 'includes/specials/SpecialMediaStatistics.php',
        'MergeHistoryPager' => 'includes/specials/SpecialMergeHistory.php',
        'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php',
        'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php',
@@@ -73,7 -73,7 +73,7 @@@ $wgConfigRegistry = array
   * MediaWiki version number
   * @since 1.2
   */
 -$wgVersion = '1.24alpha';
 +$wgVersion = '1.25alpha';
  
  /**
   * Name of the site. It must be changed in LocalSettings.php
@@@ -253,7 -253,7 +253,7 @@@ $wgFileCacheDirectory = false
  
  /**
   * The URL path of the wiki logo. The logo size should be 135x135 pixels.
 - * Defaults to "{$wgStylePath}/common/images/wiki.png".
 + * Defaults to "$wgResourceBasePath/resources/assets/wiki.png".
   */
  $wgLogo = false;
  
@@@ -859,7 -859,7 +859,7 @@@ $wgContentHandlers = array
        // dumb version, no syntax highlighting
        CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
        // simple implementation, for use by extensions, etc.
 -      CONTENT_MODEL_JSON => 'JSONContentHandler',
 +      CONTENT_MODEL_JSON => 'JsonContentHandler',
        // dumb version, no syntax highlighting
        CONTENT_MODEL_CSS => 'CssContentHandler',
        // plain text, for use by extensions, etc.
@@@ -1156,7 -1156,7 +1156,7 @@@ $wgMimeInfoFile = 'includes/mime.info'
   * Sets an external MIME detector program. The command must print only
   * the MIME type to standard output.
   * The name of the file to process will be appended to the command given here.
 - * If not set or NULL, mime_content_type will be used if available.
 + * If not set or NULL, PHP's fileinfo extension will be used if available.
   *
   * @par Example:
   * @code
@@@ -1242,6 -1242,46 +1242,46 @@@ $wgThumbnailBuckets = null
   */
  $wgThumbnailMinimumBucketDistance = 50;
  
+ /**
+  * When defined, is an array of thumbnail widths to be rendered at upload time. The idea is to
+  * prerender common thumbnail sizes, in order to avoid the necessity to render them on demand, which
+  * has a performance impact for the first client to view a certain size.
+  *
+  * This obviously means that more disk space is needed per upload upfront.
+  *
+  * @since 1.24
+  */
+ $wgUploadThumbnailRenderMap = array();
+ /**
+  * The method through which the thumbnails will be prerendered for the entries in
+  * $wgUploadThumbnailRenderMap
+  *
+  * The method can be either "http" or "jobqueue". The former uses an http request to hit the
+  * thumbnail's URL.
+  * This method only works if thumbnails are configured to be rendered by a 404 handler. The latter
+  * option uses the job queue to render the thumbnail.
+  *
+  * @since 1.24
+  */
+ $wgUploadThumbnailRenderMethod = 'jobqueue';
+ /**
+  * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom Host HTTP header.
+  *
+  * @since 1.24
+  */
+ $wgUploadThumbnailRenderHttpCustomHost = false;
+ /**
+  * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom domain to send the
+  * HTTP request to.
+  *
+  * @since 1.24
+  */
+ $wgUploadThumbnailRenderHttpCustomDomain = false;
  /**
   * Default parameters for the "<gallery>" tag
   */
@@@ -2075,28 -2115,6 +2115,28 @@@ $wgObjectCaches = array
        'hash' => array( 'class' => 'HashBagOStuff' ),
  );
  
 +/**
 + * Map of bloom filter store names to configuration arrays.
 + *
 + * Example:
 + * $wgBloomFilterStores['main'] = array(
 + *  'cacheId'      => 'main-v1',
 + *  'class'        => 'BloomCacheRedis',
 + *  'redisServers' => array( '127.0.0.1:6379' ),
 + *  'redisConfig'  => array( 'connectTimeout' => 2 )
 + * );
 + *
 + * A primary bloom filter must be created manually.
 + * Example in eval.php:
 + * <code>
 + *     BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 );
 + * </code>
 + * The size should be as large as practical given wiki size and resources.
 + *
 + * @since 1.24
 + */
 +$wgBloomFilterStores = array();
 +
  /**
   * The expiry time for the parser cache, in seconds.
   * The default is 86400 (one day).
@@@ -3102,7 -3120,7 +3142,7 @@@ $wgFooterIcons = array
        ),
        "poweredby" => array(
                "mediawiki" => array(
 -                      "src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png"
 +                      "src" => null, // Defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
                        "url" => "//www.mediawiki.org/",
                        "alt" => "Powered by MediaWiki",
                )
@@@ -3291,7 -3309,10 +3331,7 @@@ $wgResourceModuleSkinStyles = array()
   *
   * @par Example:
   * @code
 - *   $wgResourceLoaderSources['foo'] = array(
 - *       'loadScript' => 'http://example.org/w/load.php',
 - *       'apiScript' => 'http://example.org/w/api.php'
 - *   );
 + *   $wgResourceLoaderSources['foo'] = 'http://example.org/w/load.php';
   * @endcode
   */
  $wgResourceLoaderSources = array();
@@@ -3488,10 -3509,11 +3528,10 @@@ $wgResourceLoaderLESSVars = array()
   * Changes to LESS functions do not trigger cache invalidation.
   *
   * @since 1.22
 + * @deprecated since 1.24 Questionable usefulness and problematic to support,
 + *     will be removed in the future.
   */
 -$wgResourceLoaderLESSFunctions = array(
 -      'embeddable' => 'ResourceLoaderLESSFunctions::embeddable',
 -      'embed' => 'ResourceLoaderLESSFunctions::embed',
 -);
 +$wgResourceLoaderLESSFunctions = array();
  
  /**
   * Default import paths for LESS modules. LESS files referenced in @import
@@@ -3873,12 -3895,6 +3913,12 @@@ $wgMaxPPExpandDepth = 40
  
  /**
   * URL schemes that should be recognized as valid by wfParseUrl().
 + *
 + * WARNING: Do not add 'file:' to this or internal file links will be broken.
 + * Instead, if you want to support file links, add 'file://'. The same applies
 + * to any other protocols with the same name as a namespace. See bug #44011 for
 + * more information.
 + *
   * @see wfParseUrl
   */
  $wgUrlProtocols = array(
@@@ -5184,7 -5200,7 +5224,7 @@@ $wgDebugDumpSqlLength = 500
   *
   * @par Advanced example:
   * @code
 - * $wgDebugLogGroups['memcached'] = (
 + * $wgDebugLogGroups['memcached'] = array(
   *     'destination' => '/var/log/mediawiki/memcached.log',
   *     'sample' => 1000,  // log 1 message out of every 1,000.
   * );
@@@ -6188,10 -6204,8 +6228,10 @@@ $wgEnableParserLimitReporting = true
  $wgValidSkinNames = array();
  
  /**
 - * Special page list.
 - * See the top of SpecialPage.php for documentation.
 + * Special page list. This is an associative array mapping the (canonical) names of
 + * special pages to either a class name to be instantiated, or a callback to use for
 + * creating the special page object. In both cases, the result must be an instance of
 + * SpecialPage.
   */
  $wgSpecialPages = array();
  
@@@ -6317,6 -6331,7 +6357,7 @@@ $wgJobClasses = array
        'uploadFromUrl' => 'UploadFromUrlJob',
        'AssembleUploadChunks' => 'AssembleUploadChunksJob',
        'PublishStashedFile' => 'PublishStashedFileJob',
+       'ThumbnailRender' => 'ThumbnailRenderJob',
        'null' => 'NullJob'
  );
  
@@@ -746,6 -746,8 +746,8 @@@ abstract class UploadBase 
                                );
                        }
                        wfRunHooks( 'UploadComplete', array( &$this ) );
+                       $this->postProcessUpload();
                }
  
                wfProfileOut( __METHOD__ );
                return $status;
        }
  
+       /**
+        * Perform extra steps after a successful upload.
+        */
+       public function postProcessUpload() {
+               global $wgUploadThumbnailRenderMap;
+               $jobs = array();
+               $sizes = $wgUploadThumbnailRenderMap;
+               rsort( $sizes );
+               foreach ( $sizes as $size ) {
+                       $jobs []= new ThumbnailRenderJob( $this->getLocalFile()->getTitle(), array(
+                               'transformParams' => array( 'width' => $size ),
+                       ) );
+               }
+               JobQueueGroup::singleton()->push( $jobs );
+       }
        /**
         * Returns the title of the file to be uploaded. Sets mTitleError in case
         * the name was illegal.
         * @param array $attribs
         * @return bool
         */
 -      public function checkSvgScriptCallback( $element, $attribs ) {
 +      public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
 +
                list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
  
                // We specifically don't include:
                        return true;
                }
  
 +              # Check <style> css
 +              if ( $strippedElement == 'style'
 +                      && self::checkCssFragment( Sanitizer::normalizeCss( $data ) )
 +              ) {
 +                      wfDebug( __METHOD__ . ": hostile css in style element.\n" );
 +                      return true;
 +              }
 +
                foreach ( $attribs as $attrib => $value ) {
                        $stripped = $this->stripXmlNamespace( $attrib );
                        $value = strtolower( $value );
                                return true;
                        }
  
 +                      # Change href with animate from (http://html5sec.org/#137). This doesn't seem
 +                      # possible without embedding the svg, but filter here in case.
 +                      if ( $stripped == 'from'
 +                              && $strippedElement === 'animate'
 +                              && !preg_match( '!^https?://!im', $value )
 +                      ) {
 +                              wfDebug( __METHOD__ . ": Found animate that might be changing href using from "
 +                                      . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
 +
 +                              return true;
 +                      }
 +
                        # use set/animate to add event-handler attribute to parent
                        if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
                                && $stripped == 'attributename'
                        }
  
                        # use CSS styles to bring in remote code
 -                      # catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#....
 -                      $tagsList = "font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke";
                        if ( $stripped == 'style'
 -                              && preg_match_all(
 -                                      '!((?:' . $tagsList . ')\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim',
 -                                      $value,
 -                                      $matches
 -                              )
 +                              && self::checkCssFragment( Sanitizer::normalizeCss( $value ) )
                        ) {
 -                              foreach ( $matches[1] as $match ) {
 -                                      if ( !preg_match( '!(?:' . $tagsList . ')\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
 -                                              wfDebug( __METHOD__ . ": Found svg setting a style with "
 -                                                      . "remote url '$attrib'='$value' in uploaded file.\n" );
 +                              wfDebug( __METHOD__ . ": Found svg setting a style with "
 +                                      . "remote url '$attrib'='$value' in uploaded file.\n" );
 +                              return true;
 +                      }
  
 -                                              return true;
 -                                      }
 -                              }
 +                      # Several attributes can include css, css character escaping isn't allowed
 +                      $cssAttrs = array( 'font', 'clip-path', 'fill', 'filter', 'marker',
 +                              'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' );
 +                      if ( in_array( $stripped, $cssAttrs )
 +                              && self::checkCssFragment( $value )
 +                      ) {
 +                              wfDebug( __METHOD__ . ": Found svg setting a style with "
 +                                      . "remote url '$attrib'='$value' in uploaded file.\n" );
 +                              return true;
                        }
  
                        # image filters can pull in url, which could be svg that executes scripts
                return false; //No scripts detected
        }
  
 +      /**
 +       * Check a block of CSS or CSS fragment for anything that looks like
 +       * it is bringing in remote code.
 +       * @param string $value a string of CSS
 +       * @param bool $propOnly only check css properties (start regex with :)
 +       * @return bool true if the CSS contains an illegal string, false if otherwise
 +       */
 +      private static function checkCssFragment( $value ) {
 +
 +              # Forbid external stylesheets, for both reliability and to protect viewer's privacy
 +              if ( strpos( $value, '@import' ) !== false ) {
 +                      return true;
 +              }
 +
 +              # We allow @font-face to embed fonts with data: urls, so we snip the string
 +              # 'url' out so this case won't match when we check for urls below
 +              $pattern = '!(@font-face\s*{[^}]*src:)url(\("data:;base64,)!im';
 +              $value = preg_replace( $pattern, '$1$2', $value );
 +
 +              # Check for remote and executable CSS. Unlike in Sanitizer::checkCss, the CSS
 +              # properties filter and accelerator don't seem to be useful for xss in SVG files.
 +              # Expression and -o-link don't seem to work either, but filtering them here in case.
 +              # Additionally, we catch remote urls like url("http:..., url('http:..., url(http:...,
 +              # but not local ones such as url("#..., url('#..., url(#....
 +              if ( preg_match( '!expression
 +                              | -o-link\s*:
 +                              | -o-link-source\s*:
 +                              | -o-replace\s*:!imx', $value ) ) {
 +                      return true;
 +              }
 +
 +              if ( preg_match_all(
 +                              "!(\s*(url|image|image-set)\s*\(\s*[\"']?\s*[^#]+.*?\))!sim",
 +                              $value,
 +                              $matches
 +                      ) !== 0
 +              ) {
 +                      # TODO: redo this in one regex. Until then, url("#whatever") matches the first
 +                      foreach ( $matches[1] as $match ) {
 +                              if ( !preg_match( "!\s*(url|image|image-set)\s*\(\s*(#|'#|\"#)!im", $match ) ) {
 +                                      return true;
 +                              }
 +                      }
 +              }
 +
 +              if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ) {
 +                      return true;
 +              }
 +
 +              return false;
 +      }
 +
        /**
         * Divide the element name passed by the xml parser to the callback into URI and prifix.
         * @param string $element